
#pragma once

#include <stdio.h>
#include <list>
#include <algorithm>
#include "ttg/services.h"
#include "ttg/pipeline.h"

using namespace System;
using namespace ttg;
using namespace ttg::pipeline;

namespace ttg_internal
{
	namespace utils
	{
		template <class T>
		struct TestData
		{
			T value;		//current value.
			T temp;			//temporary field, which will be used for node comunication.
			size_t id;		//ID of the data (for result concatination).
		};

		//Wrapps incoming data into TestData. Also generates IDs.
		template <class T>
		class WrapperNode :public ttg::pipeline::Node<T, TestData<T>>
		{
			private:
				size_t curID;
			public:
				WrapperNode()
					:Node("WrapperNode", true, false)
				{
					//nothing
				}
				virtual void onBegin()
				{
					curID = 0;
				}
				//Processing received data.
				virtual void process(T data)
				{
					TestData<T> res;
					res.value = data;
					res.temp = data;
					res.id = curID++;
					sendNext(res);
				}
		};

		//Replaces data.temp by data.value
		template <class T>
		class TempSetterNode :public ttg::pipeline::Node<TestData<T>, TestData<T>>
		{
			public:
				//Processing received data.
				virtual void process(TestData<T> data)
				{
					data.temp = data.value;
					sendNext(data);
				}
		};

		template <class T>
		class PowerNode :public ttg::pipeline::Node<TestData<T>, TestData<T>>
		{
			public:
				//Processing received data.
				virtual void process(TestData<T> data)
				{
					data.value *= data.temp;
					sendNext(data);
				}

				//Creates wrapper, which implements operation ^power for received data.
				//Note: power must be greater or equal that 1.
				static NodeWrapper<TestData<T>, TestData<T>> createPowering(size_t power)
				{
					NodeWrapper<TestData<T>, TestData<T>> res(new TempSetterNode<T>());
					for (size_t i = 0; i < power; i++)
						res = res + wrap(new PowerNode<T>());
					return res;
				}
		};

		template <class T>
		class FactorialDividerNode :public ttg::pipeline::Node<TestData<T>, TestData<T>>
		{
			private:
				size_t n;
			public:
				FactorialDividerNode(size_t n)
				{
					this->n = n;
				}
				//Processing received data.
				virtual void process(TestData<T> data)
				{
					//Calculating factorial.
					size_t f = 1;
					for (size_t i = 2; i <= n; i++)
						f *= i;
					//Dividing.
					data.value /= f;
					sendNext(data);
				}
		};

		//Sums all items of Taylor sum. Sorts received data by ID.
		template <class T>
		class SummatorNode :public ttg::pipeline::Node<TestData<T>, T>
		{
			private:
				std::vector<T> res;
				size_t actual_size;
			public:
				SummatorNode()
					:Node("SummatorNode", true, false)
				{
					res.resize(1024);
				}
				virtual void onBegin()
				{
					for (size_t i = 0; i < res.size(); i++)
						res[i] = (T)1;
					actual_size = 0;
				}
				virtual void preEnd()
				{
					for (size_t i = 0; i < actual_size; i++)
						sendNext(res[i]);
				}
				//Processing received data.
				virtual void process(TestData<T> data)
				{
					if (data.id >= res.size())
					{
						size_t cur_size = res.size();
						res.resize(data.id);
						for (size_t i = cur_size; i < res.size(); i++)
							res[i] = (T)1;
					}
					actual_size = max(actual_size, data.id + 1);
					res[data.id] += data.value;
				}
		};

		///<summary>
		/// Performs a lot of pipeline launches with massive small data.
		/// In current implementation calculates exp(x) using big pipeline (arithmetic operation <-> node).
		///</summary>
		public ref class VerySmallData
		{
			private:
				///<summary>
				/// Creates exp(x) implementation with given count of Taylor sum items.
				/// Note: node_count must be greater that zero.
				///</summary>
				template <class T>
				static ttg::pipeline::NodeWrapper<T, T> createExpNodes(size_t item_count)
				{
					//Creating wrapper for first item x/1!
					NodeWrapper<TestData<T>, TestData<T>> temp(PowerNode<T>::createPowering(0));
					temp = temp + wrap(new FactorialDividerNode<T>(1));
					//Adding other items: x^2/2! + x^3/3! + ...
					for (size_t i = 2; i < item_count; i++)
						temp = temp *
							   (PowerNode<T>::createPowering(i - 1) +
							   wrap(new FactorialDividerNode<T>(i)));
					//Adding wrappers: T <-> TestData<T>
					NodeWrapper<T, T> res = wrap(new WrapperNode<T>()) + temp + wrap(new SummatorNode<T>());
					return res;
				}
			public:
				//
				static double expTest(size_t item_count, size_t thread_count)
				{
					if (item_count > 0 && thread_count > 0)
					{
						//Preparing pipeline
						Pipeline<double, double> *pl
							//Avast!
							= new Pipeline<double, double>(/*thread_count,*/ createExpNodes<double>(item_count));
						//Preparing data
						std::vector<double> data(128);
						for (size_t i = 0; i < data.size(); i++)
							data[i] = ttg::math::getFloatRandom(0.0f, 1.0f);
						//Launching
						double t_start = ttg::services::ServiceBase::_Timer().getMilliSeconds();
						std::vector<double> res = pl->start(data);
						double t_end = ttg::services::ServiceBase::_Timer().getMilliSeconds();
						//Checking results
						if (res.size() != data.size())
						{
							char buf[128];
							sprintf_s(buf, 127, "Wrong number of results: res_size == %d, data_size == %d",
												 res.size(),
												 data.size());
							throw Error(Error::FATAL, "VerySmallData", "expTest", buf);
						}
						sort(res.begin(), res.end());
						sort(data.begin(), data.end());
						for (size_t i = 0; i < res.size(); i++)
							if ((item_count < 4 && (res[i] < 0.9 || res[i] > 3.0)) ||
								(item_count >= 4 && Math::Abs(res[i] - Math::Exp(data[i])) > 0.01))
									throw Error(Error::FATAL, "VerySmallData", "expTest",
												"Wrong result");
						//Returning elapsed time
						SAFE_DELETE(pl);
						return t_end - t_start;
					}
					else
						return 0.0;
				}
		};
	}
}
